﻿Imports System.Net
Imports System.Xml

Partial Class TwitterClient
    Private Const StrInvalidUserListTypeReservated As String = "不正なUserListTypeが指定されました。"
    Private Enum UserListType
        Friends
        Followers
    End Enum

    ''' <summary>
    ''' すべてのページから自分がフォローしているユーザーのコレクションを取得します。
    ''' </summary>
    ''' <returns>ユーザーのコレクション</returns>
    ''' <remarks></remarks>
    Public Function GetFriends() As IList(Of TwitterUser)
        Return GetFriends(0, 0)
    End Function

    ''' <summary>
    ''' ページを指定して自分がフォローしているユーザーのコレクションを取得します。
    ''' </summary>
    ''' <param name="beginPage">取得開始ページ</param>
    ''' <param name="endPage">取得終了ページ</param>
    ''' <returns>ユーザーのコレクション</returns>
    ''' <remarks>
    ''' beginPageまたはendPageが0以下の場合はそれぞれ無視されます。
    ''' </remarks>
    Public Function GetFriends(ByVal beginPage As Integer, ByVal endPage As Integer) As IList(Of TwitterUser)
        Return GetUsers(beginPage, endPage, UserListType.Friends)
    End Function

    ''' <summary>
    ''' すべてのページから自分をフォローしているユーザーのコレクションを取得します。
    ''' </summary>
    ''' <returns>ユーザーのコレクション</returns>
    ''' <remarks></remarks>
    Public Function GetFollowers() As IList(Of TwitterUser)
        Return GetFollowers(0, 0)
    End Function

    ''' <summary>
    ''' ページを指定して自分をフォローしているユーザーのコレクションを取得します。
    ''' </summary>
    ''' <param name="beginPage">取得開始ページ</param>
    ''' <param name="endPage">取得終了ページ</param>
    ''' <returns>フォローしているユーザーのコレクション</returns>
    ''' <remarks>
    ''' beginPageまたはendPageが0以下の場合はそれぞれ無視されます。
    ''' </remarks>
    Public Function GetFollowers(ByVal beginPage As Integer, ByVal endPage As Integer) As IList(Of TwitterUser)
        Return GetUsers(beginPage, endPage, UserListType.Followers)
    End Function

    Private Function GetUsers(ByVal beginPage As Integer, ByVal endPage As Integer, ByVal type As UserListType) As IList(Of TwitterUser)
        If Not IsLogined Then
            Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
        End If
        If beginPage > endPage And endPage > 0 Then
            Throw New ArgumentOutOfRangeException("endPage", "endPageには0以下またはbeginPage以上の値のみ指定できます。")
        End If

        Dim lstUsers As New List(Of TwitterUser)
        Dim pageNo As Integer
        If (beginPage > 0) Then
            pageNo = beginPage
        Else
            pageNo = 1
        End If
        While endPage = 0 OrElse pageNo <= endPage
            Dim url As String
            Select Case type
                Case UserListType.Friends
                    url = String.Format("{0}{1}?page={2}", My.Resources.TwitterUrl, My.Resources.TwitterFriendsPath, pageNo)
                Case UserListType.Followers
                    url = String.Format("{0}{1}?page={2}", My.Resources.TwitterUrl, My.Resources.TwitterFollowersPath, pageNo)
                Case Else
                    Throw New ArgumentException(StrInvalidUserListTypeReservated, "type")
            End Select
            Dim req As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
            req.SetTwitterGetHeaders(Nothing, _cookies)
            Dim xmlRes As XmlDocument = Nothing
            Using res As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
                Select Case res.StatusCode
                    Case HttpStatusCode.OK
                        res.LoadCookies(_cookies)
                        xmlRes = res.GetResponseAsXHtml()
                    Case Else
                        Return Nothing
                End Select
            End Using
            Dim xnm As XmlNamespaceManager = xmlRes.GetXHtmlNameSpaceManager("x")
            Dim nodes As XmlNodeList = xmlRes.SelectNodes("//x:tr[starts-with(@id,'person_')]", xnm)
            For Each node As XmlNode In nodes
                Dim user As TwitterUser = ParseUser(node, xnm, "x", type)
                lstUsers.Add(user)
            Next
            If nodes.Count = 0 Then
                Exit While
            End If
            Dim nodesNextLink As XmlNodeList = xmlRes.SelectNodes("//x:div[@id='pagination']/x:a[contains(@rel,'next')]", xnm)
            If (nodesNextLink.Count > 0) Then
                pageNo += 1
            Else
                Exit While
            End If
        End While
        Return lstUsers
    End Function

    ''' <summary>
    ''' レスポンスに含まれるユーザー情報を解析します。
    ''' </summary>
    ''' <param name="node">ユーザーのルートノード</param>
    ''' <param name="xnm">ノードに関連づけられたXmlNamespaceManager</param>
    ''' <param name="prefix">プレフィックス</param>
    ''' <param name="type">ユーザーリストの種類</param>
    ''' <returns>解析されたユーザー情報</returns>
    ''' <remarks></remarks>
    Private Shared Function ParseUser(ByVal node As XmlNode, ByVal xnm As XmlNamespaceManager, ByVal prefix As String, ByVal type As UserListType) As TwitterUser
        Dim user As TwitterUser
        user = New TwitterUser()
        user.UserID = CLng(node.Attributes("id").Value.Substring(7))
        Dim nodeContact As XmlNode
        Select Case type
            Case UserListType.Friends
                nodeContact = node.SelectSingleNode(String.Format("{0}:td[@class='thumb']/{0}:a[@rel='contact']", prefix), xnm)
            Case UserListType.Followers
                nodeContact = node.SelectSingleNode(String.Format("{0}:td[@class='thumb']/{0}:div[@class='groupable']/{0}:a[@rel='contact']", prefix), xnm)
            Case Else
                Throw New ArgumentException(StrInvalidUserListTypeReservated, "type")
        End Select
        user.Language = nodeContact.Attributes("hreflang").Value
        user.UserName = nodeContact.Attributes("href").Value.Substring(19)
        Dim nodeThumb As XmlNode = nodeContact.SelectSingleNode(String.Format("{0}:img", prefix), xnm)
        user.DisplayName = nodeThumb.Attributes("alt").Value
        user.IconUrl = nodeThumb.Attributes("src").Value
        Dim nodeProfileLink As XmlNode = node.SelectSingleNode(String.Format("{0}:td/{0}:strong/{0}:a[@href='http://twitter.com/{1}']", prefix, user.UserName), xnm)
        user.Description = nodeProfileLink.Attributes("title").Value
        Dim nodeControl As XmlNode = node.SelectSingleNode(String.Format("{0}:td/{0}:div[starts-with(@id,'actions')]/{0}:div[starts-with(@id,'control')]", prefix), xnm)
        Dim nodeLockImage As XmlNode = node.SelectSingleNode(String.Format("{0}:td/{0}:img[@class='lock']", prefix), xnm)
        If nodeLockImage IsNot Nothing Then
            user.IsProtected = True
        End If
        Select Case type
            Case UserListType.Friends
                user.IsFriend = True
                Dim nodeDM As XmlNode = nodeControl.SelectSingleNode(String.Format("{0}:span/{0}:a[starts-with(@href,'/direct_messages/create/')]", prefix), xnm)
                user.IsFollower = nodeDM IsNot Nothing
            Case UserListType.Followers
                user.IsFollower = True
                user.IsFriend = nodeControl.Attributes("style") Is Nothing
        End Select
        Return user
    End Function
End Class
